﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;

using UMC.Net;

namespace UMC.Host
{
    abstract class WebSocket : UMC.Net.MimeRequest, IDisposable
    {
        public override bool IsWebSocket => true;
        public abstract void Dispose();
    }

    class HttpsWebSocket : WebSocket
    {
        public byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
        System.IO.Stream stream;
        public HttpsWebSocket(HttpMime mime, System.IO.Stream stream)
        {
            mime1 = mime;
            this.stream = stream;
            WebSocketRead();
        }
        public override async void Receive(byte[] buffer, int offset, int size)
        {
            await stream.WriteAsync(buffer, offset, size);

        }
        HttpMime mime1;
        async void WebSocketRead()
        {
            int size = 0;
            try
            {
                while ((size = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    //  var 
                    mime1.Write(buffer, 0, size);
                }
            }
            catch
            {
                this.Dispose();
                return;
            }
            this.Dispose();
        }


        public override void Dispose()
        {
            mime1.Dispose();
            stream.Close();
            stream.Dispose();
            if (buffer != null)
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
                buffer = null;
            }
        }

        protected override void Header(byte[] data, int offset, int size)
        {

        }
    }
    class HttpWebSocket : WebSocket
    {

        public byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
        Socket socket;
        public HttpWebSocket(HttpMime mime, Socket stream)
        {
            this.mime = mime;
            this.socket = stream;
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();
            eventArgs.SetBuffer(buffer);
            eventArgs.Completed += Http;
            if (!this.socket.ReceiveAsync(eventArgs))
            {
                ProcessReceive(eventArgs);
            }
        }
        HttpMime mime;

        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                try
                {
                    this.mime.Write(buffer, e.Offset, e.BytesTransferred);
                    if (!socket.ReceiveAsync(e))
                    {
                        ProcessReceive(e);
                    }
                }
                catch
                {
                    this.Dispose();
                }
            }
            else
            {
                this.Dispose();

            }
        }

        public override async void Receive(byte[] buffer, int offset, int size)
        {
            await socket.SendAsync(new ArraySegment<byte>(buffer, offset, size), SocketFlags.None);
        }
        protected override void Header(byte[] data, int offset, int size)
        {

        }
        public override void Dispose()
        {
            this.Dispose();
            try
            {
                socket.Shutdown(SocketShutdown.Both);
            }
            catch
            {

            }
            socket.Close();
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
            buffer = null;
        }
    }

    public class HttpMimeSocket : HttpMime
    {
        public override string Scheme => "http";

        public HttpMimeSocket(Socket socket, String host, String ip)
        {
            this._socket = socket;
            this.ActiveTime = UMC.Data.Utility.TimeSpan();

            this.pid = socket.GetHashCode();

            this._Host = host;

            this._remoteIpAddress = ip;

            HttpMimeServier.httpMimes.TryAdd(pid, this);
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();

            eventArgs.SetBuffer(_data);
            eventArgs.Completed += Http;
            if (!this._socket.ReceiveAsync(eventArgs))
            {
                ProcessReceive(eventArgs);
            }
        }
        Socket _socket;
        int pid = 0;
        public override int Id => pid;

        String _remoteIpAddress, _Host;
        public override String Host => _Host;
        public override String RemoteIpAddress => _remoteIpAddress;

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (isDispose == false)
            {
                try
                {

                    _socket.Send(buffer, offset, count, SocketFlags.None);
                }
                catch
                {
                    this.Dispose();
                }
            }
        }
        UMC.Net.MimeRequest request;
        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                this.ActiveTime = UMC.Data.Utility.TimeSpan();
                try
                {
                    if (request == null)
                    {
                        request = new HttpMimeRequest(this);
                    }
                    request.Receive(this._data, e.Offset, e.BytesTransferred);

                    if (this.isDispose == false && !_socket.ReceiveAsync(e))
                    {
                        ProcessReceive(e);
                    }
                }
                catch (Exception ex)
                {
                    this.OutText(500, ex.ToString());

                    return;
                }
            }
            else
            {
                this.Dispose();

            }
        }



        public override void OutputFinish()
        {

            request = null;
            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            this.TimeOut = 20;

        }
        public void Subscribe(HttpMimeRequest webRequest)
        {
            var _subscribe = UMC.Net.NetSubscribe.Subscribe(webRequest.Headers, webRequest.UserHostAddress, HttpMimeServier.Server, _socket, UMC.Data.WebResource.Instance().Provider["appSecret"]);
            if (_subscribe != null)
            {
                HttpMimeServier.httpMimes.TryRemove(pid, out var link);

                var writer = new Net.TextWriter(this.Write, _data);
                writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                writer.Write("Connection: upgrade\r\n");
                writer.Write("Upgrade: websocket\r\n");
                writer.Write($"UMC-Publisher-Key: {HttpMimeServier.Server}\r\n");
                writer.Write("Server: Apiumc\r\n\r\n");

                writer.Flush();
                writer.Dispose();
                _subscribe.Publish();
            }
            else
            {
                OutText(401, "数据订阅连接验证不通过");
            }
        }
        protected override void WebSocket(NetContext context)
        {
            if (context.Tag is NetHttpRequest)
            {
                var webr = context.Tag as NetHttpRequest;
                this.WebSocket(webr);
            }
        }
        bool isDispose = false;
        public override void Dispose()
        {
            if (isDispose == false)
            {
                if (this._data != null)
                {
                    System.Buffers.ArrayPool<byte>.Shared.Return(this._data);
                    _data = null;
                }
                isDispose = true;
                try
                {
                    _socket?.Shutdown(SocketShutdown.Both);
                    _webSocket?.Dispose();
                }
                catch
                {

                }
                _socket?.Close();
            }
            HttpMimeServier.httpMimes.TryRemove(pid, out var _);

        }
        async void WebSocket(NetHttpRequest webRequest)
        {
            try
            {
                var url = webRequest.Address;


                webRequest.Headers["Connection"] = "Upgrade";


                var client = new Socket(SocketType.Stream, ProtocolType.Tcp);

                await client.ConnectAsync(await NetProxy.DNS(url.Host), url.Port);
                // await client.ConnectAsync(url.Host, url.Port);

                if (url.Scheme == "https")
                {
                    SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                    await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);

                    await ssl.WriteAsync(_data, 0, UMC.Net.NetHttpResponse.Header(webRequest, _data));
                    int size = await ssl.ReadAsync(_data, 0, _data.Length);

                    if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                    {
                        request = _webSocket = new HttpsWebSocket(this, ssl);

                        this._socket.Send(_data, 0, size, SocketFlags.None);
                    }
                    else
                    {
                        this._socket.Send(_data, 0, size, SocketFlags.None);
                        this.Dispose();
                    }
                }
                else
                {
                    client.Send(_data, 0, UMC.Net.NetHttpResponse.Header(webRequest, _data), SocketFlags.None);
                    var size = client.Receive(_data, 0, _data.Length, SocketFlags.None);
                    if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                    {
                        request = _webSocket = new HttpWebSocket(this, client);
                        this._socket.Send(_data, 0, size, SocketFlags.None);
                    }
                    else
                    {

                        this._socket.Send(_data, 0, size, SocketFlags.None);
                        this.Dispose();
                    }

                }

                // if (url.Scheme == "https")
                // {
                //     SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                //     await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);
                //     var ws = new HttpsWebSocket(this, ssl);
                //     var size = UMC.Net.NetHttpResponse.Header(webRequest, ws.buffer);//
                //     request = ws;
                //     await ssl.WriteAsync(ws.buffer, 0, size);
                //     _webSocket = ws;
                // }
                // else
                // {
                //     var ws = new HttpWebSocket(this, client);
                //     request = _webSocket;
                //     client.Send(ws.buffer, 0, UMC.Net.NetHttpResponse.Header(webRequest, ws.buffer), SocketFlags.None);
                //     _webSocket = ws;

                // }

                HttpMimeServier.httpMimes.TryRemove(this.pid, out var _);
            }
            catch (Exception ex)
            {
                OutText(500, ex.ToString());
            }
        }
        WebSocket _webSocket;
        byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
    }
}

